﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;

using Perseus.Plugins;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Triggers;

namespace Hetymine.Executioner.Plugins.Triggers.HotKey {
    public class HotKey : TriggerPlugin, ILoader, IWindowHook {
        #region PInvoke
        /// <summary> The RegisterHotKey function defines a system-wide hot key </summary>
        /// <param name="hwnd">Handle to the window that will receive WM_HOTKEY messages 
        /// generated by the hot key.</param>
        /// <param name="id">Specifies the identifier of the hot key.</param>
        /// <param name="fsModifiers">Specifies keys that must be pressed in combination with the key 
        /// specified by the 'vk' parameter in order to generate the WM_HOTKEY message.</param>
        /// <param name="vk">Specifies the virtual-key code of the hot key</param>
        /// <returns><c>true</c> if the function succeeds; otherwise <c>false</c></returns>
        /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms646309(VS.85).aspx"/>
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

        private const int ERROR_HOTKEY_ALREADY_REGISTERED = 1409;

        [DllImport("user32", SetLastError = true)]
        private static extern int UnregisterHotKey(IntPtr hwnd, int id);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern ushort GlobalAddAtom(string lpString);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern ushort GlobalDeleteAtom(ushort nAtom);

        private const int WM_HOTKEY = 0x312;
        #endregion        
       
        //private HotKeyInfo _CommandBoxHotKey; // HotKey to show command input
        private List<HotKeyInfo> _HotKeys; // List of trigger hotkeys
        // Timer to prevent user from holding down a key and executing 
        // a command in rapid succession
        private DispatcherTimer _HotKeyTimer;

        public HotKey() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Hot Key",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/trigger/hotkey/"
            );

            // Timer to prevent rappid succession of hotkey commands
            this._HotKeyTimer = new DispatcherTimer();
            this._HotKeyTimer.Interval = TimeSpan.FromMilliseconds(400);
            this._HotKeyTimer.IsEnabled = false;
            this._HotKeyTimer.Tick += new EventHandler(HotKeyTimer_Tick);
        }

        public override ITrigger InitializeTrigger() {            
            return new HotKeyTrigger();
        }
        public override void TriggerLoaded(ITrigger trigger) {
            var hotKey = trigger as HotKeyInfo;
            
            var check = from hk in this._HotKeys
                        where hk.Key == hotKey.Key && hk.Modifiers == hotKey.Modifiers
                        select hk;

            // Register the hotkey if no matching hotkey has been registered yet
            if (check.Count() == 0) {
                this._HotKeys.Add(hotKey);

                this.RegisterHotkey(hotKey);
            }
        }

        private void RegisterHotkey(HotKeyInfo hotKey) {
            try {
                string atom = App.Current.Dispatcher.Thread.ManagedThreadId.ToString("X8") +
                    System.Windows.Input.KeyInterop.VirtualKeyFromKey(hotKey.Key);
                hotKey.Id = GlobalAddAtom(atom);
                if (hotKey.Id == 0) { throw new Exception(); }
                hotKey.Registered = true;

                if (!RegisterHotKey(
                    Executioner.Handle,
                    hotKey.Id,
                    (uint)hotKey.Modifiers,
                    (uint)System.Windows.Input.KeyInterop.VirtualKeyFromKey(hotKey.Key)
                )) { throw new Exception(); }
            }
            catch {
                if (Marshal.GetLastWin32Error() == ERROR_HOTKEY_ALREADY_REGISTERED) {
                    App.Log("The hotkey is already registered.");
                }
                else {
                    App.Log("Error registereding hotkey.");
                }

                UnregisterHotkey(hotKey);
                hotKey.Registered = false;
            }
        }
        private void UnregisterHotkey(HotKeyInfo hotKey) {
            if (hotKey != null && hotKey.Id != 0 && hotKey.Registered == true) {
                DispatcherOperation dop = App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate() {
                    UnregisterHotKey(Executioner.Handle, hotKey.Id);
                    GlobalDeleteAtom(hotKey.Id);
                });

                // In case it's a reloading, wait for all to be unregistered as to
                // prevent registering an existing hotkey
                dop.Wait();
            }
        }
        private void HotKeyTimer_Tick(object sender, EventArgs e) {
            this._HotKeyTimer.IsEnabled = false;
        }

        #region IWindowsHook
        public bool HookHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            if (msg == WM_HOTKEY) {
                if (this._HotKeyTimer.IsEnabled == true) {
                    return false;
                }

                ushort id = (ushort)wParam.ToInt32();
                this._HotKeyTimer.IsEnabled = true;

                var hotKeys = from hk in this._HotKeys
                              where hk.Id == id
                              select hk;

                // If a matching registered hotkey is found, execute all 
                // trigger commands with a matching hotkey trigger
                if (hotKeys.Count() > 0) {
                    HotKeyInfo hk = hotKeys.First();
                    var commands = from tc in Executioner.Commands["Trigger"]
                                   from hkt in ((TriggerCommand)tc).Triggers
                                   where hkt is HotKeyTrigger &&
                                   ((HotKeyTrigger)hkt).Key == hk.Key &&
                                   ((HotKeyTrigger)hkt).Modifiers == hk.Modifiers
                                   select tc as TriggerCommand;

                    foreach (TriggerCommand tc in commands) {
                        tc.Execute(string.Empty, true);
                    }
                }         

                handled = true;
            }

            return false;
        }
        #endregion
        #region ILoader
        public void Load() {
            this._HotKeys = new List<HotKeyInfo>();

            int interval = Math.Max(Executioner.Config.GetInt("Hot Key", "Timeout", 400), 250);
            this._HotKeyTimer.Interval = TimeSpan.FromMilliseconds(interval);
        }
        public void Unload() {
            // Unregister all hotkeys
            foreach (HotKeyInfo hk in this._HotKeys) {
                this.UnregisterHotkey(hk);
            }
        }
        #endregion
    }
}
